代理模式All in One

代理模式是一种特殊设计模式,在调用方与被调用方之间加上一个中间层,在许多场合中有广泛应用。

Why

对于某些业务类,其所有方法都需要执行某些前置或后置操作,比如最简单的日志,或许可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IUserDAO {
void save();
}
public class UserDAOImpl implements IUserDAO {
// some logger instance
Logger logger;
@Override
public void save() {
logger.debug("UserDAO starts ave()");
System.out.println("user saved");
logger.debug("UserDAO finishes save()");
}
}

但如果对每个业务类都进行这样的操作,一来与业务无关的代码大量重复,二来一旦日志操作发生变化,所有用到日志对象的业务类都需要进行修改。因此,我们需要代理类来完成此类处理。

静态代理

静态代理是一种简单的代理方式,需要修改调用方的业务逻辑,让调用方依赖代理对象,而不是之前的业务对象。
静态代理的实现步骤如下:

  1. 定义目标类的接口,定义业务相关的方法。
  2. 目标类实现该接口,实现业务方法。
  3. 代理类也实现该接口,同时持有目标类对象;在实现业务方法时,调用目标类对象的业务方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 代理对象
public class UserDAOProxy implements IUserDAO {
private IUserDAO target;
public UserDAOProxy(IUserDAO target) {
this.target = target;
}
@Override
public void save() {
System.out.println("begin saving...");
target.save();
System.out.println("saving completed...");
}
}
// 调用时,直接依赖于代理对象
public static void main(String[] args) throws ClassNotFoundException {
IUserDAO staticProxy = new UserDAOProxy(new UserDAOImpl());
staticProxy.save();
}

问题

静态代理虽然简单直观,但对业务是侵入式的,主要问题是:若需要对不同的类或方法进行差异化的代理,则实现较为困难。

动态代理

针对静态代理的问题,动态代理提供了优化。静态代理在编译期就确定了代理逻辑,而动态代理可以在运行时进行代理。
动态代理也称为JDK代理,在这种模式下,代理对象根据接口生成,代理类只能代理接口中定义的方法,因此需要:

  1. 目标类实现一个接口。

动态代理的步骤如下:

  1. 定义目标类的接口,定义业务相关的方法。
  2. 目标类实现该接口,实现业务方法。
  3. 代理工厂类持有目标类对象。
  4. 使用java.lang.reflect.Proxy的静态方法newProxyInstance建立针对目标类的代理对象。
    1. 该方法的第三个参数:一个InvocationHandler接口实现类的对象,需实现该接口定义的invoke方法。
    2. 在该invoke方法中利用反射,通过Method类的invoke方法调用目标类的业务方法,同时加入代理的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
// method - 目标类方法
// args - 目标类方法的参数
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("begin saving...");
Object returnV = method.invoke(target, args);
System.out.println("saving completed...");
return returnV;
}
});
}
}
// 调用
public class DynamicProxyTest{
public static void main(String[] args) throws ClassNotFoundException {
IUserDAO target = new UserDAO();
IUserDAO dynamicProxy = (IUserDAO) new ProxyFactory(target).getProxyInstance();
dynamicProxy.save();
}
}

Going Deep

让我们瞥一眼Proxy类的newProxyInstance方法,只看关键代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  @CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
{

...
// 从目标类的ClassLoader中获取代理类,或生成代理类并缓存
Class<?> cl = getProxyClass0(loader, intfs);
try {
...
// 获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
...
}
// 利用该构造方法生成代理对象
return cons.newInstance(new Object[]{h});
}
...
}

看来主要步骤如下,且最重要的是第一步:

  1. getProxyClass0方法获取代理类的Class对象。
  2. 获取代理类的构造方法。
  3. 利用构造方法生成代理对象。

那么具体产生的是什么样的代理类呢?是一个名为$Proxy0的类该类声明为public final class $Proxy0 extends Proxy implements <目标接口>(如有多个代理,则数字增加,可通过ProxyGenerator类的generateProxyClass方法手动生成字节码再反编译得到源码,在此不展开),据此可以推断:

  1. 该类继承自Proxy类,因此其父类持有的protected InvocationHandler h它也能访问到。
  2. 该类实现了目标接口的所有方法。

不难想象,该类在实现目标接口的方法时,只会做一件事:调用父类InvocationHandler对象的invoke方法。至于这个方法具体做些什么,还记得上文中ProxynewProxyInstance方法的第三个参数吗?

子类代理

子类代理即cglib代理,是一种动态代理的补充,在不需目标类实现任何接口的情况下,代理目标类的所有方法。子类代理生成一个目标类的子类对象作为代理,因此需要:

  1. 目标类提供无参构造方法。
  2. 目标类不能为final,且不能有final的方法。

首先我们重新定义下没有实现接口的目标类。

1
2
3
4
5
public class UserDAO {
public void save() {
System.out.println("user saved");
}
}

代理的步骤如下:

  1. 目标类实现业务方法。
  2. 代理类持有目标类对象。
  3. 回调响应或拦截器类实现cglib中MethodInterceptor接口的intercept方法,该拦截器将拦截父类的业务方法并执行具体的操作。
    1. 通过MethodProxy类的invokeSuper方法来调用父类的业务方法。
    2. 添加代理的逻辑。
  4. 使用cglib的Enhancer.create()方法,利用反射生成代理对象。
    1. 指定父类为目标类。
    2. 指定回调响应对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
// 只是生成代理对象的简单工厂
class CgProxyFactory {
public static Object createProxy(Object target, MethodInterceptor proxy) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(proxy);
return enhancer.create();
}
}
// 回调响应类,或称为父类方法调用拦截器
class DAOInterceptor implements MethodInterceptor {
// target - 目标类对象
// method - 目标类方法
// args - 目标类方法的参数
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("proxying by " + proxy.getClass().getName() + " before business");
Object result = proxy.invokeSuper(target, args);
System.out.println("after business");
return result;
}
}
// 调用
public class CgProxyTest {
public static void main(String[] args) throws ClassNotFoundException {
UserDAO target = new UserDAO();
MethodInterceptor interceptor = new DAOInterceptor();
UserDAO CgProxy = (UserDAO) CgProxyFactory.createProxy(target, interceptor);
CgProxy.save();
}
}

Going Deep

cglib利用字节码框架ASM,读取目标类的字节码,生成一个其子类的字节码,该子类的声明为:

1
public class UserService$$EnhancerByCGLIB$$<随机hash> extends UserDAO implements Factory

可见的确是生成了目标类的直接子类,在其中定义了两个重要的方法:

1
2
3
4
5
6
7
8
9
10
11
final void CGLIB$save$0() {
super.save();
}
public final void save() {
...
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object)this, ...);
return;
}
super.save();
}

不难看出,该子类中生成了两个方法:

  1. CGLIB$save$0方法直接调用目标类(父类)的业务方法。
  2. save方法与目标类的业务方法同名,属于重写,当发现回调对象存在时,调用回调对象的intercept方法,否则仍然直接调用目标类的业务方法。

至于回调对象及其intercept方法,还记得上文中定义的实现了MethodInterceptor接口的回调响应类吗?

应用

最典型的应用就是AOP了,通过注解或配置的方式,为目标类方法指定代理,从而动态地将非业务的逻辑代码“切入”真正的业务逻辑,省去了developer的重复劳动。